package com.snpic.appaw.module.member.service.login;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.snpic.appaw.framework.common.constants.SystemConstant;
import com.snpic.appaw.framework.common.pojo.CommonResult;
import com.snpic.appaw.module.infra.api.config.ConfigApi;
import com.snpic.appaw.module.member.constants.MemberConstant;
import com.snpic.appaw.module.member.controller.app.login.vo.LoginCodeRespVO;
import com.snpic.appaw.module.third.api.sms.SmsSenderApi;
import com.snpic.appaw.module.third.api.sms.dto.SmsReqDTO;
import com.snpic.appaw.module.third.api.sms.dto.SmsRespDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

import static com.snpic.appaw.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.snpic.appaw.module.member.constants.MemberConstant.*;
import static com.snpic.appaw.module.member.enums.ErrorCodeConstants.*;

/**
 * @author JiJun
 * @date 2025/8/26 9:19
 * @修改时间 2025/8/26 9:19
 * @description 验证码服务实现类
 * 生成6位数字验证码并存储到Redis，有效期5分钟
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class VerificationCodeServiceImpl implements VerificationCodeService {
	private final ConfigApi configInfo;

	private final StringRedisTemplate stringRedisTemplate;
	private final SmsSenderApi smsSenderApi;

	/**
	 * 生成6位数字验证码并存储到Redis（登录用）

	 */
	@Override
	public LoginCodeRespVO storeLoginCode(String phone) {
		// 调用通用方法，传入登录场景的Redis键前缀和短信模板
		return generateAndStoreVerificationCode(
			phone,
			LOGIN_CODE_COUNT_PREFIX,       // 次数统计键前缀
			LOGIN_CODE_PREFIX,             // 验证码存储键前缀
			LOGIN_CODE_VERIFY_COUNT_PREFIX,// 校验次数键前缀
			SMS_TEMPLATE_LOGIN_CODE,       // 短信模板key
			"登录用验证码"                         // 业务类型（用于日志）
		);
	}

	/**
	 * 生成6位数字验证码并存储到Redis（修改密码用）
	 */
	@Override
	public LoginCodeRespVO storeUpdatePasswordCode(String phone) {
		// 调用通用方法，传入修改密码场景的Redis键前缀和短信模板
		return generateAndStoreVerificationCode(
			phone,
			UPDATE_PASSWORD_CODE_COUNT_PREFIX,  // 次数统计键前缀
			UPDATE_PASSWORD_CODE_PREFIX,        // 验证码存储键前缀
			UPDATE_PASSWORD_CODE_VERIFY_COUNT_PREFIX, // 校验次数键前缀
			SMS_TEMPLATE_UPDATE_PASSWORD,  // 短信模板key
			"修改密码用验证码"                          // 业务类型（用于日志）
		);
	}

	/**
	 * 通用验证码生成与存储方法（抽取共性逻辑）
	 * 同一个手机号在5分钟有效期内，只能获取3次验证码，超过则提示“操作过于频繁，请1分钟后再试”
	 * 验证码只能校验5次，超过提示”“
	 * @param phone                手机号
	 * @param countKeyPrefix       次数统计键前缀（登录/修改密码区分）
	 * @param codeKeyPrefix        验证码存储redis，键前缀（登录/修改密码区分）
	 * @param verifyCountKeyPrefix 验证码校验次数，键前缀（登录/修改密码区分）
	 * @param smsTemplateKey       短信模板key（登录/修改密码区分）
	 * @param businessType         业务类型（用于日志，如"登录"、"修改密码"）
	 * @return 验证码有效期响应
	 */
	private LoginCodeRespVO generateAndStoreVerificationCode(
		String phone,
		String countKeyPrefix,
		String codeKeyPrefix,
		String verifyCountKeyPrefix,
		String smsTemplateKey,
		String businessType) {

		// 1. 定义次数统计Redis键（格式：{prefix}:手机号）
		String countKey = countKeyPrefix + phone;
		// 2. 获取当前已获取次数
		String countStr = stringRedisTemplate.opsForValue().get(countKey);
		int currentCount = countStr != null ? Integer.parseInt(countStr) : 0;
		// 3. 验证码有效期（从配置获取）
		int codeExpireMinutes = Integer.parseInt(configInfo.getConfigValue(MemberConstant.CODE_EXPIRE_MINUTES));
		// 4. 检查是否超过3次限制
		if (currentCount >= 3) {
			log.warn("{}验证码获取过于频繁，手机号：{}，当前次数：{}", businessType, phone, currentCount);
			throw exception(LOGIN_CODE_REQUEST_TOO_FREQUENT);
		}

		// 5. 生成验证码
		String verificationCode = generateLoinCode(phone);
		// 6. 存储验证码到Redis
		String codeKey = codeKeyPrefix + phone;
		try {
			stringRedisTemplate.opsForValue().set(codeKey, verificationCode, codeExpireMinutes, TimeUnit.MINUTES);
			log.info("{}验证码已存储到Redis，key：{}，有效期：{}分钟", businessType, codeKey, codeExpireMinutes);
		} catch (Exception e) {
			log.error("{}验证码存储到Redis失败，手机号：{}", businessType, phone, e);
			throw exception(VERIFICATION_CODE_REQUEST_ERROR);
		}

		// 7. 清理校验次数缓存（配合"最多输错5次需重新获取"逻辑）
		stringRedisTemplate.delete(verifyCountKeyPrefix + phone);

		// 8. 构建短信内容（templateParams,smsTemplateKey）
		String[] templateParams = {verificationCode, String.valueOf(codeExpireMinutes)};
		// 9. 发送短信
		SmsReqDTO smsReqDTO = SmsReqDTO.builder().toMobile(phone).businessType(businessType).smsTemplateKey(smsTemplateKey)
			.templateParams(templateParams).build();

		CommonResult<SmsRespDTO> commonResult = null;
		try {
			commonResult = smsSenderApi.sendSmsOne(smsReqDTO);
		} catch (Exception e) {
			log.error("{}短信发送失败，手机号：{}", businessType, phone,e);
			throw exception(SMS_SEND_FAILED);
		}
		log.info("调用短信发送接口，手机号：{}，commonResult：{}", phone, commonResult);
		if (commonResult.getCode() != SystemConstant.Zero || !"0000".equals(commonResult.getData().getReplyCode())) {
			log.error("{}短信发送失败，手机号：{}", businessType, phone);
			throw exception(SMS_SEND_FAILED);
		}

		// 10. 更新获取次数（首次设置过期时间5分钟，非首次递增）
		if (currentCount == SystemConstant.Zero) {
			stringRedisTemplate.opsForValue().set(countKey, SystemConstant.StrOne, codeExpireMinutes, TimeUnit.MINUTES);
		} else {
			stringRedisTemplate.opsForValue().increment(countKey);
		}

		return new LoginCodeRespVO().setCodeExpireMinutes(codeExpireMinutes);
	}




	/**
	 * 生成6位随机纯数字验证码,并存入redis
	 * 不能以9开头
	 * @param phone 手机号
	 * @return
	 */
	private String generateLoinCode(String phone) {
		// 生成6位随机纯数字验证码,不以9开头
		int randomNum = RandomUtil.randomInt(0, 900000); // 范围：0 ≤ randomNum < 900000
		// 格式化为6位字符串（不足补前导零）
		String code = String.format("%06d", randomNum);
		log.info("生成验证码，手机号：{}，验证码：{}", phone, code);
		return code;

	}

	/**
	 * 登陆用验证码,验证是否正确
	 *
	 * @param phone 手机号
	 * @param code  用户输入的验证码
	 * @return 验证结果 true-正确 false-错误
	 */
	@Override
	public boolean verifyLoginCode(String phone, String code) {
		//1. code不能空
		if (StrUtil.isBlank(code)) {
			return false;
		}
		//2. 获取,验证码校验次数,不能超过5次
		String verifyCountKey = LOGIN_CODE_VERIFY_COUNT_PREFIX + phone;
		String verifyCount_str = stringRedisTemplate.opsForValue().get(verifyCountKey);
		int verifyCount = verifyCount_str == null ? 0 : Integer.parseInt(verifyCount_str);
		if (verifyCount >= 5) {
			throw exception(VERIFICATION_CODE_ERROR_LIMIT_REACHED);
		}

		//3. 从Redis获取得到存储验证码
		String loginCodekey = LOGIN_CODE_PREFIX + phone;
		String storedCode = stringRedisTemplate.opsForValue().get(loginCodekey);
		if (StrUtil.isBlank(storedCode)) {
			//该手机号码验证码已经失效.
			throw exception(VERIFICATION_CODE_EXPIRED);
		}
		// 验证验证码
		boolean isMatch = code.equals(storedCode);

		if (isMatch) {
			// 验证成功后删除验证码，防止重复使用
			stringRedisTemplate.delete(loginCodekey);
			stringRedisTemplate.delete(verifyCountKey);
			log.info("验证码验证成功，已删除，手机号：{}", phone);
			// 验证成功后删除验证码次数统计
		} else {
			// 验证失败后，验证码校验次数,次数+1
			stringRedisTemplate.opsForValue().increment(verifyCountKey);
			log.warn("验证码验证失败，手机号：{}，输入验证码：{}，存储验证码：{}", phone, code, storedCode);
		}

		return isMatch;
	}


	/**
	 * 修改密码用验证码,验证是否正确
	 *
	 * @param phone
	 * @param code
	 * @return
	 */
	@Override
	public String verifyUpdatePasswordCode(String phone, String code) {
		// 1. 验证码不能为空,已在Contrrolel层校验

		// 2. 定义验证码校验错误次数的Redis键（格式：update_password:code:verify:count:手机号）
		String verifyCountKey = MemberConstant.UPDATE_PASSWORD_CODE_VERIFY_COUNT_PREFIX + phone;
		// 获取当前校验次数
		String verifyCountStr = stringRedisTemplate.opsForValue().get(verifyCountKey);
		int verifyCount = verifyCountStr == null ? 0 : Integer.parseInt(verifyCountStr);

		// 校验错误次数超过5次，抛出"验证码错误次数过多，请重新获取"异常
		if (verifyCount >= 5) {
			throw exception(VERIFICATION_CODE_ERROR_LIMIT_REACHED);
		}

		// 3. 从Redis获取存储的修改密码验证码
		String storedCodeKey = MemberConstant.UPDATE_PASSWORD_CODE_PREFIX + phone;
		String storedCode = stringRedisTemplate.opsForValue().get(storedCodeKey);
		if (StrUtil.isBlank(storedCode)) {
			// 验证码已失效
			throw exception(VERIFICATION_CODE_EXPIRED);
		}

		// 4. 验证验证码是否匹配
		boolean isMatch = code.equals(storedCode);

		if (isMatch) {
			// 验证成功：删除验证码和校验次数缓存
			stringRedisTemplate.delete(storedCodeKey);
			stringRedisTemplate.delete(verifyCountKey);
			log.info("修改密码验证码验证成功，已删除缓存，手机号：{}", phone);
			// 生成临时令牌（有效期5分钟）
    	String tempToken = UUID.randomUUID().toString();
   	  String redisKey = MemberConstant.UPDATE_PASSWORD_CODE_TOKEN_PREFIX + phone;
			 // 3. 验证码有效期（从配置获取5分钟）
			int codeExpireMinutes = Integer.parseInt(configInfo.getConfigValue(MemberConstant.CODE_EXPIRE_MINUTES));
    	stringRedisTemplate.opsForValue().set(redisKey, tempToken, codeExpireMinutes, TimeUnit.MINUTES);
			return tempToken;
		} else {
			// 验证失败：递增校验次数，无过期时间
			stringRedisTemplate.opsForValue().increment(verifyCountKey);
			log.warn("修改密码验证码验证失败，手机号：{}，输入验证码：{}，存储验证码：{}", phone, code, storedCode);
			throw exception(VERIFICATION_CODE_WRONG);
		}

	}
}
